﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Speech.Recognition;
using System.Speech.Synthesis;
using System.Windows.Forms;
using LCARSLibrary.Controls;

namespace LCARSLibrary.VerbalInterface
{
    public class VerbalInterface
    {
        #region fields

        //const float StandardConfidenceLevel = 0.75f;
        const float StandardConfidenceLevel = 0.65f;

        float recognizerCommandConfidenceLevel;
        float previousConfidenceLevel;

        DateTime previousStartTime;

        string previousRecognizedText;
        object deactivationUserState;
        object rejectTag;

        int loadingGrammarCount;
        int loadedGrammarCounter;
        int recognitionFailedCounter;

        bool active;
        bool initialized;
        bool cancelledByFunction;
        bool cancelledByRejectTimeout;

        SpeechSynthesizer speechSynthesizer;
        SpeechRecognitionEngine reBase;
        Grammar grBase;
        VIGrammarGroup currentVIGrammarGroup;
        Timer timerRejectTimeout;

        VIResult lastVIResult;

        VIMode viMode;

        List<VISpeechRecognitionEngine> viSpeechRecognitionEngines;
        IEnumerable<VIGrammarGroup> viGrammarGroups;

        #endregion

        #region properties

        public IEnumerable<VIGrammarGroup> VIGrammarGroups { get { return viGrammarGroups; } }

        public bool Initialized { get { return initialized; } }
        public bool Active { get { return active; } }
        public bool Silent { get; set; }
        public bool SoundEffectOnReject { get; set; }
        public VIMode VIMode { get { return viMode; } }

        #endregion

        #region events

        public event EventHandler<RecognizingCommandArgs> BaseCommandRecognizing;
        public event EventHandler<RecognizedCommandArgs> BaseCommandRecognized;
        public event EventHandler<DeactivationArgs> DeactivationCompleted;
        public event EventHandler InitializationCompleted;

        protected virtual void OnBaseCommandRecognizing(object sender, RecognizingCommandArgs e)
        {
            EventHandler<RecognizingCommandArgs> temp = BaseCommandRecognizing;
            if (temp != null)
            {
                temp(sender, e);
            }
        }

        protected virtual void OnBaseCommandRecognized(object sender, RecognizedCommandArgs e)
        {
            EventHandler<RecognizedCommandArgs> temp = BaseCommandRecognized;
            if (temp != null)
            {
                temp(sender, e);
            }
        }

        protected virtual void OnDeactivationCompleted(object sender, DeactivationArgs e)
        {
            EventHandler<DeactivationArgs> temp = DeactivationCompleted;
            if (temp != null)
            {
                temp(sender, e);
            }
        }

        protected virtual void OnInitializationCompleted(object sender, EventArgs e)
        {
            EventHandler temp = InitializationCompleted;
            if (temp != null)
            {
                temp(sender, e);
            }
        }

        #endregion


        public VerbalInterface(VIMode viMode)
        {
            initialized = false;
            active = false;
            this.viMode = viMode;

            timerRejectTimeout = new Timer();
            timerRejectTimeout.Tick += new EventHandler(timerTimeout_Tick);

            speechSynthesizer = new SpeechSynthesizer();

            Silent = false;
            SoundEffectOnReject = true;

            cancelledByFunction = false;
            cancelledByRejectTimeout = false;
            rejectTag = null;

            recognizerCommandConfidenceLevel = StandardConfidenceLevel;
            viSpeechRecognitionEngines = new List<VISpeechRecognitionEngine>();
        }


        public void Initialize(string baseCommand, IEnumerable<VIGrammarGroup> viGrammarGroups, int mainCommandsVIGrammarGroupID)
        {
            var groupedGrammarGroupIds = from x in viGrammarGroups
                                         group x by x.ID into g
                                         select g;

            var d = from x in groupedGrammarGroupIds
                    where x.Count() > 1
                    select x;

            if (d.Count() > 0)
                throw new ArgumentException("doppelte VIGrammarGroup ID's!");


            this.viGrammarGroups = viGrammarGroups;


            loadingGrammarCount = 2;//Base and MainCommands

            foreach (var item in viGrammarGroups)
            {
                loadingGrammarCount += item.Grammars.Count;
                loadingGrammarCount++; //GrCancel
            }

            loadedGrammarCounter = 0;

            #region Base

            grBase = CreateGrBase(baseCommand);

            reBase = new SpeechRecognitionEngine();
            reBase.UnloadAllGrammars();
            reBase.SetInputToDefaultAudioDevice();
            reBase.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(recognizer_SpeechRecognized);
            reBase.SpeechRecognitionRejected += new EventHandler<SpeechRecognitionRejectedEventArgs>(recognizer_SpeechRecognitionRejected);
            reBase.RecognizeCompleted += new EventHandler<RecognizeCompletedEventArgs>(recognizer_RecognizeCompleted);
            reBase.LoadGrammarCompleted += new EventHandler<LoadGrammarCompletedEventArgs>(recognizer_LoadGrammarCompleted);
            reBase.EmulateRecognizeCompleted += new EventHandler<EmulateRecognizeCompletedEventArgs>(recognizer_EmulateRecognizeCompleted);

            reBase.LoadGrammarAsync(grBase);

            #endregion

            SpeechRecognitionEngine speechRecognitionEngine;

            foreach (var item in viGrammarGroups)
            {
                speechRecognitionEngine = new SpeechRecognitionEngine();
                speechRecognitionEngine.UnloadAllGrammars();
                speechRecognitionEngine.SetInputToDefaultAudioDevice();
                speechRecognitionEngine.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(recognizer_SpeechRecognized);
                speechRecognitionEngine.SpeechRecognitionRejected += new EventHandler<SpeechRecognitionRejectedEventArgs>(recognizer_SpeechRecognitionRejected);
                speechRecognitionEngine.RecognizeCompleted += new EventHandler<RecognizeCompletedEventArgs>(recognizer_RecognizeCompleted);
                speechRecognitionEngine.LoadGrammarCompleted += new EventHandler<LoadGrammarCompletedEventArgs>(recognizer_LoadGrammarCompleted);
                speechRecognitionEngine.EmulateRecognizeCompleted += new EventHandler<EmulateRecognizeCompletedEventArgs>(recognizer_EmulateRecognizeCompleted);

                viSpeechRecognitionEngines.Add(new VISpeechRecognitionEngine() { ID = item.ID, SpeechRecognitionEngine = speechRecognitionEngine });

                foreach (var grammar in item.Grammars)
                    speechRecognitionEngine.LoadGrammarAsync(grammar);

                speechRecognitionEngine.LoadGrammarAsync(CreatGrCancel());

                if (item.ID == mainCommandsVIGrammarGroupID)
                    speechRecognitionEngine.LoadGrammarAsync(CreatGrMainCommands());
            }
        }


        public void Activate()
        {
            if (active || !initialized)
                return;

            active = true;
            recognitionFailedCounter = 0;
            deactivationUserState = null;

            ReturnToBase();
        }

        public void Deactivate()
        {
            Deactivate(null);
        }

        public void Deactivate(object userState)
        {
            if (!active)
                return;

            active = false;

            var re = GetCurrentSpeechRecognitionEngine();

            if (re != null)
            {
                if (re.AudioState == AudioState.Stopped)
                {
                    OnDeactivationCompleted(this, new DeactivationArgs() { UserState = userState });
                }
                else
                {
                    deactivationUserState = userState;
                    re.RecognizeAsyncCancel();
                }
            }
        }

        public void RejectCommand()
        {
            RejectCommand(null);
        }

        public void RejectCommand(object tag)
        {
            RejectCommand(null, false);
        }

        private void RejectCommand(object tag, bool byTimeout)
        {
            timerRejectTimeout.Stop();

            cancelledByFunction = true;
            rejectTag = tag;
            cancelledByRejectTimeout = byTimeout;

            var re = GetCurrentSpeechRecognitionEngine();

            if (re != null)
            {
                if (re.AudioState == AudioState.Stopped)
                {
                    currentVIGrammarGroup.OnCommandRejected(this, new RejectedCommandArgs() { VIGrammarGroup = currentVIGrammarGroup, Tag = rejectTag, ByTimeout = cancelledByRejectTimeout });
                    ReturnToBase();
                }
                else
                {
                    re.RecognizeAsyncCancel();
                }
            }
        }

        public void StartRejectTimeout(int msDuration)
        {
            StartRejectTimeout(msDuration, null);
        }

        public void StartRejectTimeout(int msDuration, object tag)
        {
            timerRejectTimeout.Interval = msDuration;
            timerRejectTimeout.Tag = tag;
            timerRejectTimeout.Start();
        }

        public void StopRejectTimeout()
        {
            timerRejectTimeout.Stop();
        }

        public bool SimulateCommand(string command)
        {
            return SimulateCommand(command, true);
        }

        public bool SimulateCommand(string command, bool sayCommand)
        {
            if (viMode != VIMode.Simulation)
                return false;

            var re = GetCurrentSpeechRecognitionEngine();

            if (re.AudioState != AudioState.Stopped)
                return false;

            if (sayCommand)
                speechSynthesizer.Speak(command);

            re.EmulateRecognizeAsync(command);

            return true;
        }


        #region internal grammars

        private Grammar CreateGrBase(string baseCommand)
        {
            if (!string.IsNullOrWhiteSpace(baseCommand))
                return new Grammar(new GrammarBuilder(baseCommand));
            else
                throw new ArgumentException("Invalid BaseCommand.");
        }

        private Grammar CreatGrCancel()
        {
            GrammarBuilder grammarBuilder = new GrammarBuilder();
            Choices choices;

            choices = new Choices();
            choices.Add("Befehl");
            choices.Add("Kommando");
            grammarBuilder.Append(choices);

            choices = new Choices();
            choices.Add("zurück");
            choices.Add("abbrechen");
            grammarBuilder.Append(choices);

            Grammar grammar = new Grammar(grammarBuilder);
            grammar.Name = "{C20DD4BF-544A-4D0E-AD0D-A21E7087B73D}";

            return grammar;
        }

        private Grammar CreatGrMainCommands()
        {
            GrammarBuilder grammarBuilder = new GrammarBuilder();
            GrammarBuilder subGrammarBuilder = new GrammarBuilder();
            Choices choices = new Choices();
            Choices subChoices = new Choices();

            //--
            subChoices = new Choices();
            subChoices.Add("Letztes");
            subChoices.Add("Letzten");
            subChoices.Add("Vorherigen");
            subChoices.Add("Vorheriges");
            subGrammarBuilder.Append(subChoices, 0, 1);

            subChoices = new Choices();
            subChoices.Add("Befehl");
            subChoices.Add("Kommando");
            subGrammarBuilder.Append(subChoices);

            subGrammarBuilder.Append(new SemanticResultValue("wiederholen", 0));
            //--
            choices.Add(subGrammarBuilder);

            choices.Add(new SemanticResultValue("Deaktivieren", 1));


            grammarBuilder.Append(choices);

            Grammar grammar = new Grammar(grammarBuilder);
            grammar.Name = "{EA0BC4C5-31BF-452D-87A0-C905B89653CE}";

            return grammar;
        }

        #endregion


        private void PlaySoundEffect(SoundEffect se)
        {
            if (!this.Silent)
                LCARSBase.PlaySoundEffect(se);
        }

        private SpeechRecognitionEngine GetCurrentSpeechRecognitionEngine()
        {
            if (currentVIGrammarGroup == null)
                return reBase;

            var re = from x in viSpeechRecognitionEngines
                     where x.ID == currentVIGrammarGroup.ID
                     select x.SpeechRecognitionEngine;

            if (re.Count() == 1)
                return re.First();
            else
                return null;
        }

        private void ReturnToBase()
        {
            currentVIGrammarGroup = null;

            if (!active || !initialized)
                return;

            if (viMode == VIMode.Default)
                reBase.RecognizeAsync(RecognizeMode.Multiple);
        }

        private void ProcessVerbalCommand(RecognitionResult recognitionResult)
        {
            bool cancelledByVerbalCommand = false;
            VIGrammarGroup nextVIGrammar = null;

            if (!active)
            {
                OnDeactivationCompleted(this, new DeactivationArgs() { UserState = deactivationUserState });
                deactivationUserState = null;
                return;
            }

            if (cancelledByFunction)
            {
                //cancelled by function call
                currentVIGrammarGroup.OnCommandRejected(this, new RejectedCommandArgs() { VIGrammarGroup = currentVIGrammarGroup, Tag = rejectTag, ByTimeout = cancelledByRejectTimeout });

                ReturnToBase();

                return;
            }

            if (recognitionResult == null)
                return;

            #region Base

            if (recognitionResult.Grammar == grBase)
            {
                RecognizingCommandArgs recognizingCommandArgs = new RecognizingCommandArgs()
                {
                    VIResult = new VIResult()
                    {
                        VIGrammarGroup = null,
                        Grammar = null,
                        Semantics = null,
                        Text = recognitionResult.Text
                    },
                    NoSoundEffect = false,
                    Cancel = false
                };
                OnBaseCommandRecognizing(this, recognizingCommandArgs);

                if (recognizingCommandArgs.Cancel)
                {
                    ReturnToBase();
                    return;
                }

                if (!recognizingCommandArgs.NoSoundEffect)
                    PlaySoundEffect(SoundEffect.Question);

                RecognizedCommandArgs recognizedCommandArgs = new RecognizedCommandArgs()
                {
                    VIResult = new VIResult()
                    {
                        VIGrammarGroup = null,
                        Grammar = null,
                        Semantics = null,
                        Text = recognitionResult.Text
                    },
                    NextVIGrammarGroup = null
                };
                OnBaseCommandRecognized(this, recognizedCommandArgs);

                nextVIGrammar = recognizedCommandArgs.NextVIGrammarGroup;
            }

            #endregion

            #region CurrentGrammar

            else if (currentVIGrammarGroup.Grammars.Contains(recognitionResult.Grammar))
            {
                RecognizingCommandArgs recognizingCommandArgs = new RecognizingCommandArgs()
                {
                    VIResult = new VIResult()
                    {
                        VIGrammarGroup = currentVIGrammarGroup,
                        Grammar = recognitionResult.Grammar,
                        Semantics = recognitionResult.Semantics,
                        Text = recognitionResult.Text
                    },
                    NoSoundEffect = false,
                    Cancel = false
                };
                currentVIGrammarGroup.OnCommandRecognizing(this, recognizingCommandArgs);

                if (recognizingCommandArgs.Cancel)
                {
                    ReturnToBase();
                    return;
                }

                if (!recognizingCommandArgs.NoSoundEffect)
                    PlaySoundEffect(SoundEffect.Yes1);

                RecognizedCommandArgs recognizedCommandArgs = new RecognizedCommandArgs()
                {
                    VIResult = new VIResult()
                    {
                        VIGrammarGroup = currentVIGrammarGroup,
                        Grammar = recognitionResult.Grammar,
                        Semantics = recognitionResult.Semantics,
                        Text = recognitionResult.Text
                    },
                    NextVIGrammarGroup = null
                };
                currentVIGrammarGroup.OnCommandRecognized(this, recognizedCommandArgs);

                nextVIGrammar = recognizedCommandArgs.NextVIGrammarGroup;

                if (nextVIGrammar == null)
                {
                    lastVIResult = new VIResult()
                    {
                        Grammar = recognitionResult.Grammar,
                        Semantics = recognitionResult.Semantics,
                        Text = recognitionResult.Text,
                        VIGrammarGroup = currentVIGrammarGroup
                    };
                }
            }

            #endregion

            #region Cancel

            else if (recognitionResult.Grammar.Name == "{C20DD4BF-544A-4D0E-AD0D-A21E7087B73D}")
            {
                timerRejectTimeout.Stop();

                cancelledByVerbalCommand = true;

                if (SoundEffectOnReject)
                    PlaySoundEffect(SoundEffect.Yes2);

                currentVIGrammarGroup.OnCommandRejected(this, new RejectedCommandArgs() { VIGrammarGroup = currentVIGrammarGroup, Tag = null, ByTimeout = false });
            }

            #endregion

            #region MainCommands

            else if (recognitionResult.Grammar.Name == "{EA0BC4C5-31BF-452D-87A0-C905B89653CE}")
            {
                ProcessMainCommands((int)recognitionResult.Semantics.Value);
            }

            #endregion

            if (!active || cancelledByFunction)
                return;

            if (cancelledByVerbalCommand || nextVIGrammar == null)
            {
                //cancelled by verbal command
                ReturnToBase();
                return;
            }

            if (viMode == VIMode.Default)
            {
                var re = from x in viSpeechRecognitionEngines
                         where x.ID == nextVIGrammar.ID
                         select x.SpeechRecognitionEngine;

                if (re.Count() == 1)
                {
                    re.First().RecognizeAsync(RecognizeMode.Multiple);
                }
            }

            currentVIGrammarGroup = nextVIGrammar;
        }

        private void ProcessMainCommands(int id)
        {
            switch (id)
            {
                case 0:
                    {
                        if (lastVIResult == null)
                        {
                            PlaySoundEffect(SoundEffect.No2);
                        }
                        else
                        {
                            RecognizingCommandArgs recognizingCommandArgs = new RecognizingCommandArgs()
                            {
                                VIResult = lastVIResult,
                                NoSoundEffect = false,
                                Cancel = false
                            };

                            lastVIResult.VIGrammarGroup.OnCommandRecognizing(this, recognizingCommandArgs);

                            if (recognizingCommandArgs.Cancel)
                            {
                                ReturnToBase();
                                return;
                            }

                            if (!recognizingCommandArgs.NoSoundEffect)
                                PlaySoundEffect(SoundEffect.Yes1);

                            RecognizedCommandArgs recognizedCommandArgs = new RecognizedCommandArgs()
                            {
                                VIResult = lastVIResult,
                                NextVIGrammarGroup = null
                            };
                            lastVIResult.VIGrammarGroup.OnCommandRecognized(this, recognizedCommandArgs);
                        }
                        break;
                    }
                case 1:
                    {
                        PlaySoundEffect(SoundEffect.Yes1);
                        Deactivate();
                        break;
                    }
            }
        }



        private void recognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            cancelledByFunction = false;
            cancelledByRejectTimeout = false;

            Console.WriteLine("{2} - Recognized: {0} (Confidence: {1})", e.Result.Text, e.Result.Confidence, DateTime.Now);


            //bei Wdhlg desselben Sprachbefehls das ConfidenceLevel um 0.2 herabsetzen,
            //falls eine Nichterkennung ohne Herabsetzen folgen würde (e.Result.Confidence < andromedaConfidenceLevel)
            //andernfallse auf den Standard zurücksetzen
            if (e.Result.Text == previousRecognizedText
                && e.Result.Confidence >= (previousConfidenceLevel - 0.2f)
                //fehlerhafte Bedingung!
                //&& e.Result.Confidence < recognizerCommandConfidenceLevel
                && e.Result.Confidence > 0.2f)
            {
                if (recognizerCommandConfidenceLevel > 0.3)
                    recognizerCommandConfidenceLevel -= 0.2f;
            }
            else
            {
                recognizerCommandConfidenceLevel = StandardConfidenceLevel;
            }

            //Aktuellen Sprachbefehl speichern, um Wdhlg zu erkennen
            previousRecognizedText = e.Result.Text;
            previousConfidenceLevel = e.Result.Confidence;

            if (e.Result.Confidence >= recognizerCommandConfidenceLevel)
            {
                //bei erfolgreicher Erkennung ConfidenceLevel wieder auf Standard stellen
                recognizerCommandConfidenceLevel = StandardConfidenceLevel;

                recognitionFailedCounter = 0;

                //Base
                if (e.Result.Grammar == grBase)
                {
                    reBase.RecognizeAsyncCancel();
                }
                //Command
                else if (currentVIGrammarGroup.Grammars.Contains(e.Result.Grammar))
                {
                    (sender as SpeechRecognitionEngine).RecognizeAsyncCancel();
                }
                //Cancel
                else if (e.Result.Grammar.Name == "{C20DD4BF-544A-4D0E-AD0D-A21E7087B73D}")
                {
                    (sender as SpeechRecognitionEngine).RecognizeAsyncCancel();
                }
                //Main Commands
                else if (e.Result.Grammar.Name == "{EA0BC4C5-31BF-452D-87A0-C905B89653CE}")
                {
                    (sender as SpeechRecognitionEngine).RecognizeAsyncCancel();
                }
            }
        }

        private void recognizer_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            Console.WriteLine("{2} - RecognitionRejected: {0} (Confidence: {1})", e.Result.Text, e.Result.Confidence, DateTime.Now);

            recognizerCommandConfidenceLevel = StandardConfidenceLevel;


            //Nach 4 missglückten Befehlen (mit mind. 10 s Pause dazwischen) VI deaktivieren
            if ((e.Result.Audio.StartTime - previousStartTime) > TimeSpan.FromSeconds(10))
            {
                if (e.Result.Confidence < recognizerCommandConfidenceLevel)
                {
                    recognitionFailedCounter++;
                    Console.WriteLine("{0} - Value of recognitionFailedCounter: {1}", DateTime.Now, recognitionFailedCounter);
                }
                else
                    recognitionFailedCounter = 0;
            }

            previousStartTime = e.Result.Audio.StartTime;

            if (recognitionFailedCounter > 3)
            {
                Console.WriteLine("{0} - Deaktiviert von recognizer_SpeechRecognitionRejected", DateTime.Now);
                Deactivate();
            }
        }


        private void recognizer_EmulateRecognizeCompleted(object sender, EmulateRecognizeCompletedEventArgs e)
        {
            ProcessVerbalCommand(e.Result);
        }

        private void recognizer_RecognizeCompleted(object sender, RecognizeCompletedEventArgs e)
        {
            ProcessVerbalCommand(e.Result);
        }


        private void recognizer_LoadGrammarCompleted(object sender, LoadGrammarCompletedEventArgs e)
        {
            if (++loadedGrammarCounter == loadingGrammarCount)
            {
                initialized = true;
                OnInitializationCompleted(this, EventArgs.Empty);
            }
        }


        void timerTimeout_Tick(object sender, EventArgs e)
        {
            timerRejectTimeout.Stop();
            RejectCommand(timerRejectTimeout.Tag, true);
        }
    }
}
